#include <winsock2.h>
#include <ws2tcpip.h>
#include "pThreads/semaphore.h"
#include <vector>
#include "CSemaphore.h"

#include "CBasicPacket.h"
#include "CSearchResponsePacket.h" 
#include "CBlowfish.h"
#include "define.h"
#include "CConsole.h"

CSearchResponsePacket::CSearchResponsePacket(void)
{
	data=NULL;
	dataEnciphered=NULL;
	numEntries=0;
	memset(entries,0,20*sizeof(struct seaResponseEntry));
	totalPlayersFound=0;
	memset(entrySize,0,20*sizeof(unsigned char));
	setLastPacket(false);
}

CSearchResponsePacket::~CSearchResponsePacket(void)
{
}

char* CSearchResponsePacket::getData(void)
{
	generateData();
	return data;
}

char* CSearchResponsePacket::getDataEnciphered(void)
{
	generateData();
	if (dataEnciphered!=NULL)
		delete dataEnciphered;
	
	dataEnciphered=new char[size];
	encipher();

	return dataEnciphered;
}

void CSearchResponsePacket::setData(char* newData,unsigned short length)
{
	if (data!=NULL)
		delete data;

	data=new char[length];
	memcpy(data,newData,length);
	size=length;

    parseData();
}

char* CSearchResponsePacket::getKey(void)
{
	return &key[0];
}

void CSearchResponsePacket::setKey(char* newKey)
{
	memcpy(&key[0],newKey,24);
}

unsigned int CSearchResponsePacket::getSize(void)
{
	calculateSize();
	return size;
}

void CSearchResponsePacket::parseData(void)
{
	if (data==NULL)
		generateData();

	CConsole::outDebOnly("Packetsize: %d",*((unsigned long*)&data[0]));
	endOfData=*((unsigned short*)&data[8]);
	CConsole::outDebOnly("Packetsize(header+search data): %d",endOfData);
	if (data[0xA]==0x80)
		CConsole::outDebOnly("Last search response packet");
	else
		CConsole::outDebOnly("Not last search response packet");
	totalPlayersFound=*((unsigned short*)&data[0x0E]);
	CConsole::outDebOnly("Number of players found: %d",totalPlayersFound);

	unsigned short currBit=0;
	unsigned short startOfNextEntry=0;
	unsigned char entryNum=0;

	unsigned char entryType;

	struct seaResponseEntry newEntry;

	FILE* outfile;
	outfile=fopen("./data/searchResponse_parsed.txt","w+");

	//0x18 = header + variables
	while( (currBit/8) < (endOfData-0x18))
	{
		memset(&newEntry,0,sizeof(newEntry));

		fprintf(outfile,"\nENTRY %d:\n",entryNum);
		fprintf(outfile,"Start at Byte 0x%.4X (Bit %d)\n",(currBit)/8+0x18,currBit+0x18*8);

		entrySize[entryNum]=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x18],currBit,8);
		currBit+=8;
		startOfNextEntry=currBit+(entrySize[entryNum]*8);

		fprintf(outfile,"Size: %d\n",entrySize[entryNum]);

		while (currBit < startOfNextEntry)
		{
			if ((currBit+5)>=startOfNextEntry)
			{
				currBit=startOfNextEntry;
				break;
			}

			entryType=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x18],currBit,5);
			currBit+=5;

			switch (entryType)
			{
				case SEARCH_NAME:
				{
					if ((currBit+4)>=startOfNextEntry)
					{
						currBit=startOfNextEntry;
						break;
					}
					unsigned char nameLen=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x18],currBit,4);
					currBit+=4;
					for (int c=0;c<nameLen;c++)
					{					
						newEntry.name[c]=(char)CBasicPacket::unpackBitsLE(&data[0x18],currBit,7);
						currBit+=7;
					}
					newEntry.name[nameLen]='\0';
					fprintf(outfile,"Name: %s\n",newEntry.name);
					break;
				}
				case SEARCH_AREA:
				{
					newEntry.area=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x18],currBit,8);
					currBit+=8;
					fprintf(outfile,"Area: %.2X\n",newEntry.area);
					break;
				}
				case SEARCH_NATIONALITY:
				{
					newEntry.country=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x18],currBit,2);
					currBit+=2;
					fprintf(outfile,"Nation %.2X\n",newEntry.country);
					break;
				}
				case SEARCH_JOB:
				{
					
					newEntry.mainjob=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x18],currBit,5);
					currBit+=5;
					newEntry.subjob=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x18],currBit,5);
					currBit+=5;
					fprintf(outfile,"Jobs: %.2X/%.2X\n",newEntry.mainjob,newEntry.subjob);
					break;
				}
				case SEARCH_LEVEL:
				{
					newEntry.mainjobLevel=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x18],currBit,8);
					currBit+=8;
					newEntry.subjobLevel=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x18],currBit,8);
					currBit+=8;
					fprintf(outfile,"Levels: %d/%d\n",newEntry.mainjobLevel,newEntry.subjobLevel);
					break;
				}
				case SEARCH_RACE:
				{
					newEntry.race=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x18],currBit,4);
					currBit+=4;
					fprintf(outfile,"Race: %d\n",newEntry.race);;
					break;
				}
				case SEARCH_RANK:
				{
					newEntry.rank=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x18],currBit,8);
					currBit+=8;
					fprintf(outfile,"Rank: %d\n",newEntry.rank);
					break;
				}
				case SEARCH_FLAGS1:
				{
					newEntry.flags1=(unsigned short)CBasicPacket::unpackBitsLE(&data[0x18],currBit,16);
					fprintf(outfile,"Flags1: 0x%.4X\n",newEntry.flags1);
					if ((newEntry.flags1&0x4000)==0x4000)
						fprintf(outfile,"ANONYM\n");
					currBit+=16;
					break;
				}
				case SEARCH_FLAGS2:
				{
					newEntry.flags2=(unsigned int)CBasicPacket::unpackBitsLE(&data[0x18],currBit,32);
					fprintf(outfile,"Flags2: 0x%.8X\n",newEntry.flags2);
					currBit+=32;
					break;
				}
				case SEARCH_COMMENT:
				{
					newEntry.comment=(unsigned int)CBasicPacket::unpackBitsLE(&data[0x18],currBit,32);
					fprintf(outfile,"Comment: 0x%.8X\n",newEntry.comment);
					currBit+=32;
					break;
				}
				case SEARCH_UNKNOWN0x08:
				{
					newEntry.unknown0x08=(unsigned int)CBasicPacket::unpackBitsLE(&data[0x18],currBit,20);
					fprintf(outfile,"0x08: 0x%.5X\n",newEntry.unknown0x08);
					currBit+=20;
					break;
				}
				case SEARCH_LINKSHELLRANK:
				{
					newEntry.linkshellRank=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x18],currBit,8);
					//CBasicPacket::packBitsLE(respPacket2,0LL,currBit,8);
					fprintf(outfile,"Linkshell rank: 0x%.2X\n",newEntry.linkshellRank);
					currBit+=8;
					break;
				}
				case SEARCH_UNKNOWN0x0E:
				{
					newEntry.unknown0x0E=(unsigned int)CBasicPacket::unpackBitsLE(&data[0x18],currBit,32);
					//CBasicPacket::packBitsLE(respPacket2,0LL,currBit,32);
					fprintf(outfile,"0x0E: 0x%.8X\n",newEntry.unknown0x0E);
					currBit+=32;
					break;
				}
				case SEARCH_LANGUAGE:
				{
					newEntry.languages=(unsigned short)CBasicPacket::unpackBitsLE(&data[0x18],currBit,16);
					fprintf(outfile,"Languages: 0x%.4X\n",newEntry.languages);
					currBit+=16;
					break;
				}
				default:
				{
					fprintf(outfile,"Unknown Entry: %.2X\n",entryType);
					fprintf(outfile,"Bits left: %d\n",startOfNextEntry-currBit);
					currBit=startOfNextEntry;
				}
			}
		}

		memcpy(&entries[entryNum],&newEntry,sizeof(newEntry));
		//printf("Entry Name: %s\n",entries[entryNum].name);
		entryNum++;
	}
	numEntries=entryNum;
	fclose(outfile);
}

//calculate size:
void CSearchResponsePacket::calculateSize(void)
{
	int newSize=0;
	newSize+=0x08; //Header
	newSize+=0x04; //Key

	int workloadSize=0;
	workloadSize+=0x10; //Variables

	unsigned int entrySizeBits;
	for(int entry=0;entry<numEntries;entry++)
	{
		entrySizeBits=0;
		//Name
		entrySizeBits+=5+4+(unsigned int)strlen(entries[entry].name)*7;
		//Area
		entrySizeBits+=5+8;
		//flags1
		entrySizeBits+=5+16;
		//flags2
		entrySizeBits+=5+32;
		//languages
		entrySizeBits+=5+16;
		//0x08
		entrySizeBits+=5+20;
		//0x0D
		entrySizeBits+=5+8;
		//0x0E
		entrySizeBits+=5+32;
		//comment
		if (entries[entry].comment!=0x00000000)
			entrySizeBits+=5+32;

		if (((entries[entry].flags1&0x4000)==0x0000) && ((entries[entry].flags2&0x00004000)==0x0000)) //not anon
		{
			//country
			entrySizeBits+=5+2;
			//rank
			entrySizeBits+=5+8;
			//job
			entrySizeBits+=5+5+5;
			//level
			entrySizeBits+=5+8+8;
			//race
			entrySizeBits+=5+4;
		}

		//Round to next multiple of 8
		if ((entrySizeBits%8) > 0)
			entrySizeBits+=((8-(entrySizeBits%8)));

		//Set Entry Size
		entrySize[entry]=entrySizeBits/8;

		//Add to workload Size
		workloadSize+=entrySize[entry];
	}

	workloadSize+=numEntries;//1 Byte for each Entry Size

	endOfData=workloadSize;
	endOfData+=0x08; // Header Size needs to be added to endOfData, but is not part of the workload

	workloadSize+=0x10; //Hash

	if ((workloadSize%0x80) > 0)
		workloadSize+=(0x80-(workloadSize%0x80));

	newSize+=workloadSize;

	size=newSize;
}

void CSearchResponsePacket::setLastPacket(bool value)
{
	if (value)
		lastPacket=0x80;
	else
		lastPacket=0x00;
}

unsigned char sizeLR = 1;

void CSearchResponsePacket::generateData(void)
{
	/*
	sizeLR++;
	if (sizeLR>32)
		sizeLR = 32;
	CConsole::outDebOnly("SizeLR: %d",sizeLR);
	*/

	if (data!=NULL)
		delete data;

	calculateSize();
	data = new char[size];
	memset(data,0,size);

	//Write Header
	memcpy(&data[0x00],&size,4);
	memcpy(&data[0x04],"XIFF",4);

	//Write Globals
	memcpy(&data[0x08],&endOfData,2);
	memcpy(&data[0x0A],&lastPacket,1);
	unsigned char ucVal=0x80;
	memcpy(&data[0x0B],&ucVal,1);
	unsigned short usVal=0x0000;
	memcpy(&data[0x0C],&usVal,2);
	memcpy(&data[0x0E],&totalPlayersFound,2);
	unsigned int uiVal=0x00000000;
	memcpy(&data[0x10],&uiVal,4);
	memcpy(&data[0x14],&uiVal,4);

	unsigned short currBit=0;
	unsigned short written=0;
	//Write Entries
	for(unsigned char entry=0;entry<numEntries;entry++)
	{
		written=currBit;
		//entry Size
		CBasicPacket::packBitsLE(&data[0x18],entrySize[entry],currBit,8);
		currBit+=8;

		//Name
		CBasicPacket::packBitsLE(&data[0x18],SEARCH_NAME,currBit,5);
		currBit+=5;
		CBasicPacket::packBitsLE(&data[0x18],strlen(entries[entry].name),currBit,4);
		currBit+=4;
		for(unsigned char c=0;c<strlen(entries[entry].name);c++)
		{
			CBasicPacket::packBitsLE(&data[0x18],entries[entry].name[c],currBit,7);
			currBit+=7;
		}
		
		//Area
		CBasicPacket::packBitsLE(&data[0x18],SEARCH_AREA,currBit,5);
		currBit+=5;
		CBasicPacket::packBitsLE(&data[0x18],entries[entry].area,currBit,8);
		currBit+=8;

		if ((entries[entry].flags1&0x4000)==0x0000) //not anonymous
		{
			//country
			CBasicPacket::packBitsLE(&data[0x18],SEARCH_NATIONALITY,currBit,5);
			currBit+=5;
			CBasicPacket::packBitsLE(&data[0x18],entries[entry].country,currBit,2);
			currBit+=2;

			//job
			CBasicPacket::packBitsLE(&data[0x18],SEARCH_JOB,currBit,5);
			currBit+=5;
			CBasicPacket::packBitsLE(&data[0x18],entries[entry].mainjob,currBit,5);
			currBit+=5;
			CBasicPacket::packBitsLE(&data[0x18],entries[entry].subjob,currBit,5);
			currBit+=5;

			//level
			CBasicPacket::packBitsLE(&data[0x18],SEARCH_LEVEL,currBit,5);
			currBit+=5;
			CBasicPacket::packBitsLE(&data[0x18],entries[entry].mainjobLevel,currBit,8);
			currBit+=8;
			CBasicPacket::packBitsLE(&data[0x18],entries[entry].subjobLevel,currBit,8);
			currBit+=8;

			//race
			CBasicPacket::packBitsLE(&data[0x18],SEARCH_RACE,currBit,5);
			currBit+=5;
			CBasicPacket::packBitsLE(&data[0x18],entries[entry].race,currBit,4);
			currBit+=4;

			//rank
			CBasicPacket::packBitsLE(&data[0x18],SEARCH_RANK,currBit,5);
			currBit+=5;
			CBasicPacket::packBitsLE(&data[0x18],entries[entry].rank,currBit,8);
			currBit+=8;
		}
		//flags1
		CBasicPacket::packBitsLE(&data[0x18],SEARCH_FLAGS1,currBit,5);
		currBit+=5;
		CBasicPacket::packBitsLE(&data[0x18],entries[entry].flags1,currBit,16);
		currBit+=16;

		//0x08
		CBasicPacket::packBitsLE(&data[0x18],SEARCH_UNKNOWN0x08,currBit,5);
		currBit+=5;
		CBasicPacket::packBitsLE(&data[0x18],entries[entry].unknown0x08,currBit,20);
		currBit+=20;

		//linkshell rank => itemId - 0x200
		CBasicPacket::packBitsLE(&data[0x18],SEARCH_LINKSHELLRANK,currBit,5);
		currBit+=5;
		CBasicPacket::packBitsLE(&data[0x18],entries[entry].linkshellRank,currBit,8);
		currBit+=8;

		//0x0E
		CBasicPacket::packBitsLE(&data[0x18],SEARCH_UNKNOWN0x0E,currBit,5);
		currBit+=5;
		CBasicPacket::packBitsLE(&data[0x18],entries[entry].unknown0x0E,currBit,32);
		currBit+=32;

		//comment
		if (entries[entry].comment!=0x00000000)
		{
			CBasicPacket::packBitsLE(&data[0x18],SEARCH_COMMENT,currBit,5);
			currBit+=5;
			CBasicPacket::packBitsLE(&data[0x18],entries[entry].comment,currBit,32);
			currBit+=32;
		}

		//flags2
		CBasicPacket::packBitsLE(&data[0x18],SEARCH_FLAGS2,currBit,5);
		currBit+=5;
		CBasicPacket::packBitsLE(&data[0x18],entries[entry].flags2,currBit,32);
		currBit+=32;

		//languages
		CBasicPacket::packBitsLE(&data[0x18],SEARCH_LANGUAGE,currBit,5);
		currBit+=5;
		CBasicPacket::packBitsLE(&data[0x18],entries[entry].languages,currBit,16);
		currBit+=16;

		if ((currBit%8)>0)
			currBit+=(8-(currBit%8));
	}

	//Write Hash
	int toHash=size; //whole packet
	toHash-=8;//-headersize
	toHash-=16;//-hashsize
	toHash-=4;//-keysize

	md5((unsigned char*)(&data[0x08]), (unsigned char*)(&data[size-0x14]), toHash);

	//Write Key
	memcpy(&data[size-0x04],&key[0x10],4);
}

void CSearchResponsePacket::encipher(void)
{
	memcpy(&dataEnciphered[0],&data[0],size);
	unsigned char hash[16];
	unsigned char tmp;
	unsigned long P[18];
	unsigned long S[4][256];

	md5((unsigned char*)(key), hash, 24);

	CBlowfish::init((char *)hash,16,P,S[0]);

	tmp = (size-12)/4;
	tmp -= tmp%2;

	for(unsigned char i = 0; i < tmp; i += 2) 
	{
		CBlowfish::encipher((unsigned long *)dataEnciphered+i+2, (unsigned long *)dataEnciphered+i+3,P,S[0]);
	}
}

void CSearchResponsePacket::dumpPacket(int filenum)
{
	calculateSize();
	generateData();

	char filename[256];
	sprintf(filename,"./data/seaGenOut_%.1X.dat",filenum);

	FILE* p=fopen(filename,"wb+");
	if (p)
	{
		fwrite(data,1,size,p);
		fclose(p);
	}
}

void CSearchResponsePacket::setNumberOfPlayersFound(unsigned short numFound)
{
	totalPlayersFound=numFound;
}

void CSearchResponsePacket::setEntry(unsigned char entryNum,struct seaResponseEntry* newEntry)
{
	memcpy(&entries[entryNum],newEntry,sizeof(struct seaResponseEntry));
}

void CSearchResponsePacket::setNumberOfEntries(unsigned char newNum)
{
	numEntries=newNum;
}